package com.chucang.shucang.common.security.service;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;

import com.chucang.shucang.common.base.constant.CacheConstant;
import com.chucang.shucang.common.base.constant.SecurityConstant;
import com.chucang.shucang.common.base.utils.OpsUtil;
import com.chucang.shucang.common.security.entity.OauthClientDetailsEntity;
import com.chucang.shucang.common.security.exception.OAuthClientException;
import com.chucang.shucang.common.security.feign.RemoteClientDetailsService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;

/**
 * @author flitsneak
 * @email flitsneak@gmail.com
 * @date 2022/9/18 1:27
 * @description 加载客户端信息
 */
@RequiredArgsConstructor
public class ShuCangRemoteRegisteredClientRepository implements RegisteredClientRepository {
    /**
     * 刷新令牌有效期默认 30 天
     */
    private static final int REFRESH_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 30;

    /**
     * 请求令牌有效期默认 12 小时
     */
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60 * 12;

    private final RemoteClientDetailsService clientDetailsService;

    @Override
    public void save(RegisteredClient registeredClient) {
        throw new UnsupportedOperationException();
    }

    @Override
    public RegisteredClient findById(String id) {
        throw new UnsupportedOperationException();
    }

    /**
     * 重写原生方法支持redis缓存
     *
     * @param clientId 客户端id
     * @return 客户端信息
     */
    @Override
    @SneakyThrows
    @Cacheable(value = CacheConstant.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
    public RegisteredClient findByClientId(String clientId) {

        OauthClientDetailsEntity clientDetails = OpsUtil
                .of(clientDetailsService.getClientDetailsById(clientId, SecurityConstant.FROM_IN))
                .getData()
                .orElseThrow(() -> new OAuthClientException("clientId 不合法"));

        RegisteredClient.Builder builder = RegisteredClient
                .withId(clientDetails.getClientId())
                .clientId(clientDetails.getClientId())
                .clientSecret(SecurityConstant.NOOP + clientDetails.getClientSecret())
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);

        // 授权模式
        Optional.ofNullable(clientDetails.getAuthorizedGrantTypes())
                .ifPresent(grants -> StringUtils.commaDelimitedListToSet(grants)
                        .forEach(s -> builder
                                .authorizationGrantType(new AuthorizationGrantType(s))
                        )
                );
        // 回调地址
        Optional.ofNullable(clientDetails.getWebServerRedirectUri())
                .ifPresent(redirectUri -> Arrays
                        .stream(redirectUri.split(StrPool.COMMA))
                        .filter(StrUtil::isNotBlank)
                        .forEach(builder::redirectUri));

        // scope
        Optional.ofNullable(clientDetails.getScope())
                .ifPresent(
                        scope -> Arrays
                                .stream(scope.split(StrPool.COMMA))
                                .filter(StrUtil::isNotBlank)
                                .forEach(builder::scope)
                );

        return builder
                .tokenSettings(TokenSettings
                        .builder()
                        .accessTokenFormat(OAuth2TokenFormat.REFERENCE)
                        .accessTokenTimeToLive(Duration.ofSeconds(Optional
                                        .ofNullable(clientDetails.getAccessTokenValidity())
                                        .orElse(ACCESS_TOKEN_VALIDITY_SECONDS)
                                )
                        )
                        .refreshTokenTimeToLive(
                                Duration
                                        .ofSeconds(Optional.ofNullable(clientDetails.getRefreshTokenValidity())
                                                .orElse(REFRESH_TOKEN_VALIDITY_SECONDS)
                                        )
                        )
                        .build())
                .clientSettings(ClientSettings
                        .builder()
                        .requireAuthorizationConsent(!BooleanUtil.toBoolean(clientDetails.getAutoApprove()))
                        .build()
                )
                .build();
    }
}
